﻿using Devil;
using System.Collections.Generic;
using UnityEngine;

namespace GameToolkit
{
    [CreateAssetMenu(fileName = "MoveAbility", menuName = "角色控制/Character Based Movment")]
    public class CharacterBasedMove : ActorFeatureDescriptor
    {
        enum JumpAct : byte
        {
            Idle,
            Holding,
            Jump,
        }

        [SerializeField]
        InputVectorDescriptor m_MoveInput;

        [SerializeField]
        InputActionDescriptor m_JumpInput;

        [SerializeField, Range(1, 10), Tooltip("一倍速度")]
        float m_MoveSpeed = 3.5f;


        [SerializeField, Range(0f, 0.1f), Tooltip("响应移动输入临界值")]
        float m_MoveThreshold = 0.01f;

        [SerializeField, Range(1, 3), Tooltip("冲刺速度倍率")]
        float m_SprintMultipier = 1.5f;

        [SerializeField, Tooltip("加速度")]
        float m_Acceleration = 10;

        [SerializeField, Tooltip("角速度")]
        float m_Angular = 200;

        [SerializeField, Min(0.1f), Tooltip("跳跃高度系数")]
        float m_JumpHeightMultipier = 1;

        [SerializeField, Tooltip("蓄力时间-跳跃高度映射关系")]
        AnimationCurve m_JumpHeightByHoldTime;

        [SerializeField, Tooltip("旋转动画参数偏移量-延时")]
        AnimationCurve m_RotationDelayTime;

        [SerializeField, AnimParamType(AnimatorControllerParameterType.Float)]
        AnimParam m_AnimForward;

        [SerializeField, AnimParamType(AnimatorControllerParameterType.Float)]
        AnimParam m_AnimAngular;

        public override ActorBehaviour.IFeature Creaet()
        {
            return new MoveAbility(this);
        }

        class MoveAbility : ActorBehaviour.IAbilityWithInput, ActorBehaviour.IDefaultPoseture
#if UNITY_EDITOR
            , IDebugContext
#endif
        {


#if UNITY_EDITOR
            Object IDebugContext.GetContext()
            {
                return mCfg;
            }
#endif

            ActorBehaviour mActor;
            CharacterBasedMove mCfg;
            CharacterController mController;
            Vector3 mDesiredV;
            Vector3 mVelocity;
            float mJumpPower;
            JumpAct mJump;
            float mMoveCharacterThreshold;
            ActorBehaviour.EPosture mPosture = ActorBehaviour.EPosture.Standing;
            int ActorBehaviour.IDefaultPoseture.ExecutionOrder => 0;

            Vector3 ActorBehaviour.IPoseture.Velocity => mVelocity;

            ActorBehaviour.EPosture ActorBehaviour.IPoseture.PosetureState => mPosture;

            int ActorBehaviour.IAbilityWithInput.Priority => 0;

            internal MoveAbility(CharacterBasedMove mov)
            {
                mCfg = mov;
            }

            void ActorBehaviour.IFeature.OnFeatureEnable(ActorBehaviour actor)
            {
                mActor = actor;
                if (mController == null)
                {
                    mController = actor.GetComponent<CharacterController>();
                }
                if (mController != null)
                    mMoveCharacterThreshold = mController.minMoveDistance * mController.minMoveDistance;
                else
                    mMoveCharacterThreshold = 0;
            }

            void ActorBehaviour.IFeature.OnFeatureDisable(ActorBehaviour actor)
            {
                mJump = JumpAct.Idle;
                mJumpPower = 0;
            }

            bool ActorBehaviour.IAbilityWithInput.ExecuteInput(ActorBehaviour.IAbilityInput input)
            {
                Vector3 mov;
                if (input.Descriptor == mCfg.m_MoveInput && input.GetValue(out mov))
                {
                    mDesiredV = mov;
                    mDesiredV.y = 0;
                    return true;
                }
                else if (input.Descriptor == mCfg.m_JumpInput && mPosture == ActorBehaviour.EPosture.Standing)
                {
                    float power;
                    if (input.GetValue(out power))
                    {
                        mJumpPower = power;
                        mJump = input.IsDown ? JumpAct.Jump : JumpAct.Idle;
                    }
                    else if (input.IsDown)
                    {
                        mJump = JumpAct.Holding;
                        mJumpPower = 0;
                    }
                    else if (input.IsUp)
                    {
                        mJump = JumpAct.Jump;
                    }
                    return true;
                }
                else
                {
                    return false;
                }
            }

            void ActorBehaviour.IAbilityWithInput.GetUsedInputs(ICollection<Object> inputsDescriptor)
            {
                if (mCfg == null || mController == null)
                    return;
                if (mCfg.m_MoveInput != null)
                    inputsDescriptor.Add(mCfg.m_MoveInput);
                if (mCfg.m_JumpInput != null)
                    inputsDescriptor.Add(mCfg.m_JumpInput);
            }

            bool ActorBehaviour.IAbility.IsInterruptable(ActorBehaviour.IAbility interruptSource)
            {
                return true;
            }

            void ActorBehaviour.IAbility.OnActivate()
            {
            }

            void ActorBehaviour.IAbility.OnDeactivate()
            {
            }

            void ActorBehaviour.IPoseture.OnEnter(Vector3 initVelocity, ActorBehaviour.EPosture initPoseture)
            {
                mVelocity = initVelocity;
                mPosture = initPoseture;
            }

            void ActorBehaviour.IPoseture.OnExit()
            {
            }

            bool ActorBehaviour.IAbility.OnUpdateAbility(float deltaTime)
            {
                return mDesiredV.sqrMagnitude > mCfg.m_MoveThreshold;
            }

            void ActorBehaviour.IPoseture.OnUpdatePoseture(float deltaTime)
            {
#if UNITY_EDITOR
                if (mController != null)
                    mMoveCharacterThreshold = mController.minMoveDistance * mController.minMoveDistance;
#endif
                var trans = mActor.transform;
                var point = trans.position;
                var v = mVelocity;
                var posture = mPosture;
                var gravity = Physics.gravity;
                // take off
                if(mJump == JumpAct.Jump && posture == ActorBehaviour.EPosture.Standing)
                {
                    float height = mCfg.m_JumpHeightMultipier;
                    if(mCfg.m_JumpHeightByHoldTime!=null && mCfg.m_JumpHeightByHoldTime.length > 0)
                    {
                        height *= mCfg.m_JumpHeightByHoldTime.Evaluate(mJumpPower);
                    }
                    v.y = Mathf.Sqrt(2 * Mathf.Abs(gravity.y * height));
                    posture = ActorBehaviour.EPosture.Falling;
                }
                var mov = mDesiredV.sqrMagnitude > mCfg.m_MoveThreshold;
                var forward = trans.forward;
                Quaternion rot = trans.rotation;
                var nextV = v + gravity * deltaTime;
                if(posture == ActorBehaviour.EPosture.Standing)
                {
                    // angular
                    if (mov)
                    {
                        var angle = MathExt.RotateAngleFromTo(forward, mDesiredV, Vector3.up);
                        var max = mCfg.m_Angular * deltaTime;
                        if (angle > max)
                            angle = max;
                        else if (angle < -max)
                            angle = -max;
                        rot = Quaternion.AngleAxis(angle, Vector3.up) * rot;
                        forward = rot * Vector3.forward;
                    }
                    Vector3 desiredV;
                    if (mov)
                        desiredV = mDesiredV * Vector3.Dot(forward, mDesiredV.normalized);
                    else
                        desiredV = Vector3.zero;
                    var deltaV = desiredV - nextV;
                    var maxV = mCfg.m_Acceleration * deltaTime;
                    if(deltaV.sqrMagnitude > maxV * maxV)
                    {
                        deltaV = deltaV.normalized * maxV;
                        desiredV = nextV + deltaV;
                    }
                    nextV.x = desiredV.x;
                    nextV.z = desiredV.z;
                }

                var movVector = (v + nextV) * deltaTime * 0.5f;
                if (movVector.sqrMagnitude > mMoveCharacterThreshold)
                {
                    var hits = mController.Move(movVector);
                    if (mController.isGrounded)
                    {
                        nextV.y = 0;
                        posture = ActorBehaviour.EPosture.Standing;
                    }
                    else
                    {
                        posture = ActorBehaviour.EPosture.Falling;
                    }
                }
                trans.rotation = rot;
                mVelocity = nextV;
                mDesiredV = Vector3.zero;
                mPosture = posture;
            }
        }
    }
}
